home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-11-06 | 41.0 KB | 975 lines | [TEXT/MSET] |
- Note: You need not read this file to begin using and experimenting with
- the Selection Framework, although it is recommended that you do so.
-
- If you want to quickly get going to see how it all works, then simply do the
- following:
-
- 1) Make sure you are using Mops version 2.5 or later.
-
- 2) Modify your "Mops.paths" file to include the "Selection ƒ 2.5" folder.
-
- 3) Place the file "Selection.rsrc" in your "Mops ƒ" folder (this file contains a
- a resource needed by the picture demos).
-
- 4) Load the file "Load Std Environment" into Mops. This a file load script
- that will load all of the needed files for the Selection Framework.
-
- 5) Just start browsing the provided files. At the end of each file will be
- an example of how to use the new classes. I kind of like the editlist
- class. You'll catch on pretty quickly.
-
-
- THE SELECTION FRAMEWORK
-
- By Douglas B. Hoffman
- 06Nov94
- CompuServe 72310,1743
-
- 565 Countryside Lane
- Oakland, MI 48363
- USA
-
- The Macintosh user interface typically presents the user with a window that
- contains several "selectable" objects from which the user can choose. The
- user chooses an object by using the mouse to click on it. The object will
- then hilite itself somehow indicating that it is currently selected, and finally
- the user can perform some operation on that object such as sending keystrokes,
- cutting or pasting information via the clipboard, etc. Also, some
- objects, such as controls, may always be "active" and will immediately respond
- to a click of the mouse without first having to be hilited. Additionally,
- when the window deactivates certain objects should change their visual appearance
- along with the window (e.g. controls should deactivate).
-
- This type of consistent behavior makes things very easy for the user. Unfortunately,
- creating this behavior makes things difficult for the programmer.
- However, as you will soon see, the object oriented approach can greatly simplify
- the programmers task in handling the Macintosh user interface. With
- these files we present a framework, known as the Selection Framework (SF), that
- takes care of the details by doing the right things at the right times and in
- such a way that your programming work on the interface is minimized. We would
- prefer that you spend your time working on the aspects of your program that
- make it unique rather than wrestling with standardized interface issues (and
- we assume that you would prefer this also!).
-
- The framework presented here is a rather simple one and does not try to do too
- much. It is believed that the most valuable programming frameworks are those
- that are easily understandable by other programmers. Extremely elegant and
- complex frameworks may have the potential to do more, but there is usually
- also a large investment in learning time required by the programmer in order
- to use it. So, we have attempted to provide a clear, consistent, and yet simple
- framework for window behavior. By using this framework, Mops programmers
- can gain maximum leverage of the work of others.
-
-
- Selection Windows
-
- A selection window, or selWindow is a primitiveWin subclass that maintains a
- list of selectable objects. When the new: message is sent to a selWindow the
- selWindow will send a new: message to each selection object in the list
- ( selList ). Also, the window pointer is passed via the stack along with the
- new: message. The reason for this is that many selection objects, such as
- controls and text edit fields, require a reference to their owning window.
- This is a requirement of the Macintosh toolbox. We pass the window reference
- even to those objects that don't require it, and of course those objects will
- simply ignore it. Additionally, the selWindow will send the draw: message to
- each object immediately after new: so the objects have a chance to place their
- image on the screen.
-
- The list of selectable objects in each selWindow must of course be initially
- "loaded" with the appropriate objects. There are two basic ways to do this.
-
- First, we can instantiate public objects and add them to the selWindow's list
- simply by passing a reference to the object along with the add: message to
- the window. We must do this for each object to be added to the list. For example:
-
-
- selWindow aSelWindow
- test: aSelWindow \ the window must be new before we can add objects
-
- selobject aSelobject1
- selobject aSelobject2
- selobject aSelobject3
-
- aSelobject1 add: aSelWindow
- aSelobject2 add: aSelWindow
- aSelobject3 add: aSelWindow
-
-
- Alternatively, we can have our selection objects be instance variables of the
- selWindow. In this case we must redefine the selWindow's new: method to add
- references from each selection ivar to the list. For example:
-
- :class tSelWindow super{ selWindow }
- selobject aSelobject1
- selobject aSelobject2
- selobject aSelobject3
-
- :m new:
- new: super
- aSelobject1 add: super
- aSelobject2 add: super
- aSelobject3 add: super
- ;m
-
- ;class
-
- Note that adding ivars to the selList is very similar to adding public objects.
- The differences are that in the window's new: method we must first
- call new: super, then add references to the objects.
-
- Using ivars as selection objects is slightly more complex than for public objects,
- but we don't think it to be difficult. The advantage of using ivars
- will occur if several windows are desired because each window will automatically
- "instantiate" its own selection objects and add them to the list, rather
- than having to explicitly do this for each new window.
-
- The selWindow class also keeps track of which object in the selList is currently
- selected by storing a copy of the reference in an ivar currentSel ( a
- var). We again take advantage of late binding by sending messages to the ivar
- currentSel without knowing exactly which object is referenced. But we do know
- that any object placed there will properly respond to a standard set of messages.
- Of course it is up to you, the programmer, to assure that all of your
- selection objects can handle these messages. As you will see, it is quite
- easy to design objects that can accept these messages, especially if we design
- our selection objects as subclasses of the nullSelect class (more on that
- later). The currently selected object will receive special treatment from the
- selWindow. All keystrokes and clipboard actions will apply only to the currently
- selected object. This behavior is more than likely what you will always
- want. For example, if your window contains several different text edit
- type objects, you would likely prefer that any keystrokes be sent to only one
- of those objects at a time, the currently selected object. Also, the idle:
- message will be sent (very frequently, as a result of null events) only to the
- currently selected object. The idle: message will give your currently selected
- object a chance to do things like blink a text edit caret or perhaps
- change the shape of the mouse cursor as it moves over the object. The standard
- set of clipboard messages are , of course, cut:, copy:, paste:, and clear:.
-
- All objects in the selList will receive the same messages at certain times, as
- is appropriate. These messages include new:, activate:, deactivate:, draw:,
- and release:. We already discussed the new: message. The deactivate: message
- will be sent to all selection objects every time the window is disabled by the
- user (when another window is selected with the mouse or any other means). The
- activate: message will be sent only to all selection objects that always remain
- active every time the window is enabled by the user (when our window is
- selected with the mouse or any other means). The draw: message will be sent to
- each selection object whenever the Macintosh system asks the window to draw
- itself. The release: message will be sent to each selection object when the
- user has asked the window to be closed. This will allow each object to properly
- dispose of any heap memory that has been allocated to it.
-
- The most interesting behavior of a selWindow occurs when the window receives a
- mouse click. This will generate a content: message to the window, from the
- Macintosh and Mops systems, which in turn will lead to invoking the doContent:
- method. The purpose of the doContent: method is to take care of the selection
- behavior. That is, when the user selects with the mouse a different object in
- the window, the window will disable the previously selected object, enable the
- newly selected object, and make the newly selected object the "current" object,
- if appropriate, so that keystrokes and clipboard actions will
- be directed to it. Some objects do not respond to keystrokes or clipboard actions,
- so our framework treats them slightly differently. More on that later.
- Also, doContent: has the task of determining which object in the selList was
- "hit", if any, and sending the click: message to that object.
-
- So we can see that the doContent: method is doing a lot of things and is very
- important to the implementation of the Selection Framework. It will be useful
- to examine the content: and doContent: methods of the selWindow class in detail.
-
- :m content:
- active: self
- IF
- doContent: self
- ELSE
- select: self
- THEN ;m
-
- As we can see above, when a content: message is received by a selWindow, a
- check is made to see if the window is already active. If not active, then we
- merely select the window, which is the default behavior anyway. If the window
- was already active, then we must determine if any of our selection objects
- have been "hit" (selected with the mouse), by invoking the window's doContent:
- method.
-
- :m doContent: { \ next -- }
- start: SelList \ Must look for a hit on an object in the list.
- BEGIN
- next: SelList
- WHILE
- -> next
- hit?: next
- IF
- \ You hit something. Is it already the current selection?
- next get: currentSel =
- IF
- \ You hit the current selection, so do its click method.
- click: next ( next = currentSel )
- ELSE
- \ you hit a different object, so...
- focus?: next IF
- \ we must change the focus to the next object
- get: currentSel deactivate: **
- activate: next
- draw: next
- next put: currentSel
- ELSE
- \ don't change the focus, just do a click:
- click: next
- THEN
- THEN
- exit \ must stop this method here
- THEN
- REPEAT
- ;m
-
- We can see that the method above uses a BEGIN/WHILE/REPEAT loop to step
- through each object in the selList of the window, starting with the first object.
- Actually, the loop will stop at the first hit, so we might not step
- through every object. We step through each object in the selList by using the
- start: and next: methods, where start: will cause the ensuing use of next: to return
- the first object in the list. Subsequent calls to next: will return the
- next objects in the list, in order. Note that the next: method will also return
- a boolean which indicates if we reached the end of the list (false = end
- of list). So our BEGIN/WHILE/REPEAT loop is terminated when next: returns
- false (end of list).
-
- The WHILE portion of our loop will first check to see if the mouse "hit" that
- particular object in the list, and if it did then send the object a click: message
- and/or activate: message as appropriate. Also, the current selection
- ivar is maintained as appropriate, and the last selected object may receive a
- deactivate: message. That's it! We hope that the Mops programmer wishing to
- use the SF will study this behavior so you know what is happening. Modifications
- to this behavior should not be difficult.
-
-
- Selection Objects
-
- Objects to be used as selection objects must adhere to a specific behavior
- when certain messages are received. We use the class nullSelect as a way to
- ensure that all selection classes subclassed from nullSelect contain all of
- the required selection methods. Below is the definition of the nullSelect
- class, from which we can subclass a selection object. Lets look at each required
- message response in detail to gain a better understanding of the SF.
-
- new: must always expect a window pointer (wptr) on the stack, whether or not
- our selection object requires it. In nullSelect we simply drop it, but if
- your object requires the wptr, as many Mac toolbox constructs do (e.g. text
- edit and controls), then you should override the new: method and use the wptr
- as appropriate.
-
- hit?: is perhaps the most important selection method to understand. Your selection
- object must respond to a hit?: message by testing for whether the
- user has clicked on your object or not. Also, if the user has clicked on your
- object, then you must also indicate if the focus should be given to your object.
- The focus?: method will tell if a particular object should receive the
- focus by being placed in the currentSel ivar. If we hit an object and that
- object demands the focus, then the framework will send the click: message to
- the object and make it the current selection. The current selection will be
- the focus of any keystrokes or clipboard commands as sent by the user. By
- looking at the idle:, key:, cut:, copy:, paste:, and clear: methods for class
- selWindow we can see how the current selection is always passed these messages,
- late bound, by the window when the window receives the same message from
- the system. See below.
-
- :CLASS selWindow super{ primitiveWin }
- var currentSel \ the currently selected object; could be a nullSelect
- 10 List SelList \ a list of objects that can be selected
-
- ...
-
- :m idle:
- get: currentSel idle: ** ;m
-
- :m key: ( char -- )
- get: currentSel key: ** ;m
-
- :m cut:
- get: currentSel cut: ** ;m
-
- :m copy:
- get: currentSel copy: ** ;m
-
- :m paste:
- get: currentSel paste: ** ;m
-
- :m clear:
- get: currentSel clear: ** ;m
-
- ...
-
- So all of our selection objects must also be able to handle these six focus
- messages. Of course we can always choose to have our objects "ignore" these
- messages by simply doing nothing. But these messages must still be present in
- the class definition.
-
-
- When our selection window is sent the new: message, it will in turn send the
- new: message to each selection object in the selList. The selection window
- will also pass, on the stack, its own window pointer in case the selection object
- requires that (e.g. controls, text edit objects, List Manager objects, etc.).
-
-
- :CLASS nullSelect super{ object }
-
- \ we always pass the window pointer, whether it is needed or not, for
- consistency
- :m new: ( wptr -- ) drop ;m
- :m release: ;m
-
- :m activate: ;m
- :m deactivate: ;m
-
-
- :m idle: initCursor ;m
- :m draw: ;m
- :m key: ( char -- ) drop ;m
-
- :m hit?: ( -- f ) false ;m
- :m click: ;m
- :m focus?: ( -- f ) false ;m
- :m alwaysActive?: ( -- t ) true ;m
-
- :m cut: ;m
- :m copy: ;m
- :m paste: ;m
- :m clear: ;m
-
- ;CLASS
-
-
-
-
-
- Using Selection Objects
-
-
- PREDEFINED SELECTION CLASSES
-
- In the following section the various standard selection classes are described.
- Each and every method for the classes are not described because that is best
- learned by inspecting the actual source code. Also, examples of how to use each
- class are provided at the end of each source code listing.
-
- The selection classes were designed to make your work in programming the Macintosh
- user interface much easier. As such, implementing any of the selection
- classes is extremely easy and no matter which selection class you use the
- way you use it is consistent for all selection classes.
-
- StaticText
-
- StaticText, or non-editable text in the sense that it is not TextEdit text, is
- text that is meant for display. StaticText remembers everything it needs to
- draw itself including the position in the window, the font, fontsize, etc. A
- maximum of 16 characters will fit in a StaticText object.
-
- Note the heavy reliance on multiple inheritance.
-
-
- PushButton and checkBox+
-
- A pushButton and checkBox+ will function as is without any required setup.
- They have default titles and positions, and will respond appropriately to
- mouse clicks. Action handlers must be installed in order for these controls
- to actually do anything.
-
-
- RadioGroup
-
- A radioGroup is an array of radio buttons, we normally would never use a radio
- button except as part of a group. The buttons are vertically aligned and
- behave as expected. We optionally use init: to set the initial position and
- which button will be the first to be set. We use get: to obtain which button
- is now on, get: returns the zero-based index of the currently selected button.
-
-
- VscrollBar and Hscrollbar
-
- The scrollbars are fully functional and will respond to mouse clicks with no
- additional setup required. Scrolling is accomplished by sending late bound
- messages to a second object, the object to be scrolled. The default object to
- be scrolled is nullOwnerObject which accepts the required messages from the
- scrollbar prescroll:, postscroll:, and draw:. We can have the scrollbar
- control any other object by using the scrolledby: method. Of course we must
- define methods that behave properly to the scrollbar messages.
-
- See the use of scrollbars in classes tescroll, editlist, and pictscroll.
-
- Note that the number of pixels to scroll and the max and min control values
- must be set up as well. Default values are provided that are reasonable.
- Perhaps we should have a method that inspects the object to be scrolled for
- those values? Also, the rectangle to scroll must be set up via
- setscrollrect:.
-
-
- Text Edit Classes
-
- Class terec is strictly intended for use as a way to conveniently access the
- Mac toolbox data structure for a TextEdit record. As can be seen in class te,
- we don't instantiate a terec object or ivar. Instead, we obtain a pointer to
- the record ( ptr: TEHandle, in this case) and then pass a message to the class
- itself!
-
- Note that we normally would subclass from te, as we do in tebox and tescroll.
- A te is a selection object and perhaps is one of the best examples of the advantages
- of using the selection framework. We can have as many te objects as
- we wish in a window and they will all behave as expected in that each must
- first be selected with the mouse and then keystrokes may be received. All we
- need do is instantiate as many te objects as we wish and then add: them to a
- selwindow. Of course we should at least reposition the te objects (use move:
- or moveto:) so that they do not all lay on top of each other.
-
- Also note that the clipboard is supported automatically.
-
- A tebox is simply a te with a black border.
-
- A tescrollV is a textbox with a vertical scrollbar so we can scroll through
- multi-line text in the standard Macintosh manner.
-
-
- List Manager
-
- The ListManager class is intended for subclassing. A 1 column list, a list-col
- is based on the ListManager. We treat the list as if it were a 1-
- dimensional array and use the access methods to: and at: in the expected
- fashion, with the address and length of the string being the passed data.
-
- Edit List
-
- An editlist is a scrollable/editable row and column matrix of text data.
- While resembling our ListManager class behavior, note that we do *not* use any
- ListManager routines at all. Of course we follow our selection protocol so
- use is very simple and we can have multiple editlists in a window. One unique
- feature of an editlist is that the TextEdit field will appear *in* the current
- cell being edited.
-
- Note that classinit: contains all of the setup parameters (number of rows and
- columns and so forth). Default is 30 rows and 30 columns, but we don't
- display all at once. We can scroll through them. A subclass could easily
- have different values.
-
- Note also that we provide for a filterprocedure: method that could be over
- ridden to inspect data from a cell after it is entered, or any time we attempt
- to leave that cell. Here the filterprocedure: method simply returns true.
- Returning false would result in the user being returned to the offending cell
- until acceptable data was entered.
-
- Like our TextEdit objects, we can have multiple editlists, or editlists and
- TextEdit objects in the same window and the standard Macintosh behavior will
- automatically occur.
-
-
- Popup Menu
-
- Class popup allows us to easily have popup menus in a selwindow by following
- our standard selection protocol. Popups are subclassed from the standard Mops
- menu class so storing xts for the popup to execute is just like for standard
- menus. One unique feature of the popup class is we need not specify a menu ID
- because the new: method will simply choose a unique ID for us, we don't care
- which.
-
- Also, in keeping with our philosophy that a minimum, preferably no, setup
- should be required to use our objects, class popup will function quite nicely
- simply by instantiating and adding to our selwindow.
-
-
- Picture Classes
-
-
-
-
- DESIGNING YOUR OWN SELECTION CLASSES
-
- Perhaps two of the most difficult concepts presented here are designing
- a selection object and designing an object that implements scrolling. So
- the following will provide some insight into a process that might be used for
- designing both.
-
- Suppose we wish to have a selection object that will allow us to view a picture.
- In addition, we wish to be able to view pictures that are larger than
- the view area that we have allowed in our window. Clearly, that means we need
- to use something like scrollbar controls. We wish the use of our new class of
- objects to fit the standard protocol that we have defined, so the use of an
- object of the class pictScroll would be as follows:
-
- pictScroll p
-
- selwindow w
- p add: w
- test: w
-
- When we execute test: w we want a fully functional selection object to appear
- in a window, ready for action. All necessary behavior will occur automatically
- (calling new: at the right time, drawing at the right time, releasing memory
- at the right time, etc.) So, following our general strategy for all objects,
- the default behavior for a pictScroll object will be to load itself
- with an available test pict resource and use reasonable values for positioning
- itself and displaying itself in a window. Naturally, we can always change the
- resource ID and size and positioning if we choose.
-
- The first decision we make whenever we design a new class is which superclass(s)
- to use. A scrolling picture seems to be a lot like a picture, so we
- decide to start with the picture class as superclass. This is very convenient
- because we notice that the picture class already inherits from both the nullSelect
- class and the ownerobj class. So we know that any objects we instantiate
- can already be used as both a selection object and a scrollbar owner object
- without defining any more methods! The advantage to doing it this
- way is if we forget to define one of the required methods then our program
- will still run because all protocol messages would be recognized by our object
- (but of course there will be no appropriate response for meaningful behavior),
- instead of having Mops stop everything and reporting an error. Sometimes
- we could even crash and need to restart our computer. This would be
- very disruptive to our creative programming process.
-
- The next decision we make is what instance variables should be used? Obviously,
- we need two scrollbar controls. Additionally, we decide it would look
- nice to frame the rectangle in which we view the picture with a black border.
- So we can now begin to define our pictScroll class as follows:
-
- :class pictScroll super{ picture }
- vscrollbar VScrollControl
- hscrollbar HScrollControl
- graphicRect frame
-
- ;class
-
- pictScroll p
-
-
- Now we can instantiate a pictscroll object, p, and add: it to a selwindow.
- Test: ing the selwindow will result in no errors because our new selection object
- will respond to every required message. Of course p will not do any
- scrolling yet, nor can we even see the scrollbars on the screen because we
- have not redefined any of the superclass methods. But we can verify at this
- time that we have a valid pict resource that will properly show up on the
- screen. So for now our pictScroll object behaves exactly like a picture object.
-
- Next we decide that we should attempt to get the scrollbars and the frame to
- draw: themselves. So we define a draw: method for class pictScroll, overriding
- the superclass draw:. We also recognize that we must send the new: message
- to our scrollbar ivars before we can draw: them, so we must also define a
- new: method. We are careful to observe the convention that all new: methods
- for selection objects will require the window pointer (wptr) on the stack at
- new: time. We are also very careful to call new: super so all of the pict
- new: action will occur.
-
- :m new: { wptr -- }
- wptr new: super
- wptr new: VScrollControl
- wptr new: HScrollControl
- ;m
-
- We remember that we should redefine release: so that the memory for the
- scrollbars is properly returned when we close the test window. Again, we remember
- to call the superclass release: method (in this case, we return memory
- occupied by the PICT resource).
-
- :m release:
- release: super
- release: VScrollControl
- release: HScrollControl
- ;m
-
-
- At this time it dawns on us that we should define a default reasonable rectangle
- for our picture to be viewed in. So we use classinit: to set up our
- graphicRect ivar (frame).
-
- :m classinit:
- 50 50 150 150 put: frame
- ;m
-
-
- Now we feel we are ready to define our draw: method, whose task is solely to
- place the image of our object on the screen. We call draw: super so the
- picture will draw itself (our superclass is picture, remember).
-
- :m draw:
- draw: super
- draw: frame
- draw: VScrollControl
- draw: HScrollControl
- ;m
-
-
- Again, we instantiate our object, p, and test it in a window. We are pleased
- to see that now both of our scrollbars appear and our frame appears. We are
- slightly dismayed, but should not be surprised, that everything is out of position
- and our scrollbars do not respond to mouse clicks. But that's ok, we
- actually have quite a bit of code up and running already. Our code is properly
- making all of the necessary toolbox calls to create, display, and destroy
- pictures and scrollbars in a window of our choice. That code is doing a lot
- already, and was not very difficult to create! We continue on, somewhat
- inspired at this point.
-
- So our next task is to properly position the objects. We decide to create a
- new method just for doing this, and call it position:. We notice that we can
- use position: in our classinit: method.
-
- :m position:
- get: frame { l t r b -- }
- r 1- ( x) t ( y) b t - ( len) init: VScrollControl
- l ( x) b 1- ( y) r l - ( len) init: HScrollControl
- l t moveto: super
- ;m
-
- :m classinit:
- classinit: super
- 50 50 150 150 put: frame
- position: self
- ;m
-
- Making these changes, we see that everything looks to be positioned properly on the
- screen (Actually, we probably have used a little trial and error because it is easy
- to be off by 1 pixel here or there. Forth is so interactive and it is so easy and
- fast to make changes that there is little need for an "interface drawing" tool).
-
- Wait a minute! We forgot to clip the drawing of the picture to the frame. No
- problem, simply call ClipRect with appropriate values just before and just
- after draw: ing the picture. So we change our draw: method as follows:
-
-
- :m draw:
- get: frame put: temprect 1 1 inset: temprect temprect call ClipRect
- draw: super
- 0 0 32000 32000 put: temprect temprect call ClipRect \ no clipping
- draw: frame
- draw: VScrollControl
- draw: HScrollControl
- ;m
-
- A quick check verifies that we are now properly clipping the picture drawing.
-
- So now we are ready to define the methods that allow our object to respond to
- user input, in this case mouse input. We start by defining our hit?: and
- click: methods. Hit?: tests to see if the mouse was clicked on our object, if
- it was then the selWindow will send a click: method our selobject which in
- turn must send a click: message to the appropriate scroll control. Note that
- we will only test to see if the mouse hit one of our two controls. We decide
- that a hit to the picture itself (the frame rectangle) has no meaning for this
- type of object.
-
- :m hit?: ( -- f )
- hit?: VScrollControl
- hit?: HScrollControl or
- ;m
-
- :m click:
- hit?: VScrollControl IF click: VScrollControl THEN
- hit?: HScrollControl IF click: HScrollControl THEN
- ;m
-
-
- Note we are having to retest for which control was hit in our click: method.
- We could save a little code and a little time by incorporating the click: message
- sending in our hit?: method, but we choose to keep our code consistent
- and understandable. Besides, Mops is plenty fast and compact enough to allow us to be
- slightly redundant.
-
- Testing these two new methods shows that we do indeed cause our scrollbars to
- respond to the mouse. But the picture does not yet scroll!
-
- So, referring to the recipe for scrolling, we see that, at a minimum, we must
- do two things: 1) tell the scrollbar(s) what ownerObject to use, and 2) tell
- the scrollbar(s) the rectangle that should be scrolled. Setting the ownerObject
- must be done every time we run our program, so we must do this in our
- new: method and not our classinit: method. This is a very important point to
- remember. The reason is because the ownerObject reference is actually an address.
- Since all addresses can, and usually do, change every time we run a
- program (one never knows where in the Mac's memory a program will be loaded),
- we must "reset" this address every time. The ownerObject is stored in the
- scroll control so the scroll control can send certain messages to it at just
- the right time. This approach to scrolling has the advantage of decoupling
- the scrollbar code from our other code (in this case the pictScroll code). So
- we need not define custom scroll controls that will only work with certain
- code.
-
- Redefining our pictScroll new: method to include putOwnerObject: goes like
- this:
-
- :m new: { wptr -- }
- ... as before ...
-
- self putOwnerObject: VScrollControl
- self putOwnerObject: HScrollControl
- ;m
-
- Setting the rectangles to scroll can be done anytime, not just when new, so we
- choose a convenient place to do it, here the position: method seems convenient.
- Also, we set the same rectangle values for both scrollbars, which is
- appropriate for a pictscroll type object.
-
- :m position:
- ... as before ...
-
- l t r b put: temprect 1 1 inset: temprect
- get: temprect setScrollRect: VScrollControl
- get: temprect setScrollRect: HScrollControl
- ;m
-
- Retesting our code shows that something like what we want for scrolling the
- picture definitely happens, but it isn't quite right. The picture looks like
- it is trying to scroll, but when we release the mouse the picture appears to
- not have scrolled at all! It is only now that we remember we must do one more
- thing... We must redefine our draw: method to always move the picture relative
- to the scrollbar control values. So our new draw: method looks like
- this:
-
- :m draw:
- get: frame { l t r b -- }
- l get: HScrollControl - ( x)
- t get: VScrollControl - ( y) moveTo: super \ picture moveto: method
-
- ... as before ...
- ;m
-
- Retesting our code shows that we really can now scroll our picture! The only
- "fine tuning" remaining to be done is to set the range for our scrollbars so
- our picture will scroll all the way to its edges, and not scroll "too far".
- Since we are using pixel based scrolling, we should set the scroll controls'
- ranges to a low of zero and a high corresponding to the width or height of the
- picture. We can set these values in our position: method.
-
- :m position:
- ... as before ...
-
- 0 ( lo) width: super width: frame - 5 + ( hi) putrange: HScrollControl
- 0 ( lo) height: super height: frame - 5 + ( hi) putrange: VScrollControl
- ;m
-
- Note that we could also change the number of pixels to scroll by using the
- scroll control setScrollValues: method. But the default values seem to work
- just fine for us here.
-
- Also, we should define activate: and deactivate: methods so our pictscroll objects
- will properly activate: and deactivate: upon window enable: and disable:.
-
-
- :m activate:
- activate: VScrollControl
- activate: HScrollControl ;m
-
- :m deactivate:
- deactivate: VScrollControl
- deactivate: HScrollControl ;m
-
-
-
- OTHER CLASSES and SUPPORT WORDS
-
- In this section we describe the many support classes and words for the Selection
- Framework that are included in this specially extended version of Mops.
- In several instances the classes are subclasses of existing standard Mops
- classes (e.g. point+ and rect+) that do not alter existing method behavior but
- add additional required methods. As such, it would be possible to simply redefine
- the original classes. This would save a little dictionary space. But
- we leave these as separate classes in order to clearly distinguish what is
- different with these Mops extensions.
-
- File DBH Mops Extensions
-
- mDrawString ( addr len -- )
- mStringWidth ( addr len -- width )
-
- The Macintosh Toolbox traps DrawString and StringWidth are very useful and
- used extensively in our version of Mops. Both traps require a $ptr (string
- pointer, or Str255 data type). In keeping with the Mops convention of always
- using strings in the address length form, we define the Mops word equivalents
- of the toolbox traps by appending an "m" to the front of each name ( m stands
- for Mops DrawString and Mops StringWidth). We could have, of course, simply
- named the words the same as the toolbox traps but this would be confusing to
- the programmer who is familiar with the trap as described in Inside Macintosh.
-
- number>$ ( n -- addr len)
-
- Number-to-string is used to convert any non floating point number on the stack
- to a string. Negative numbers will have a minus sign appended to the front of
- the string.
-
-
- >heap and >dispose
-
- >heap and >dispose allow us to dynamically create nameless objects from the
- heap in the same way Neon did. The advantage to using this method over, for
- example OBJHANDLE, is that we can simply send messages straight to the object
- if we store the returned pointer in a value or local variable (we need not
- first send the obj: method first). Of course since we are locking a handle we
- should be careful not too create too many of these objects such that the heap
- is fragmented. This should not be a problem for relatively small or transient
- objects created with >heap.
-
- The Clipboard
-
- Clipboard menu handlers for our standard selection environment are provided
- in the file named Clipstuff. We only implement text data, but of course we
- could generalize to pict data if we wish. We define the words selcut, selcopy,
- selpaste, and selclear which simply send late-bound messages to the
- currently active Mops window.
-
- Event+
-
- In class event+ we provide a few new methods for class event. We should probably
- just revise our event class to contain these methods. Try this from the
- Mops front end window with and without the shift key
- pressed: shiftKey?: fevent .
-
- We also provide the methods commandKey?: capslockKey?: and optionKey?: which
- behave in an analogous manner to shiftKey?: in that we simply pass the corresponding
- message to object fevent in order to determine the state of the Macintosh
- modifier keys.
-
- Also note that we actually changed the class of object fevent.
-
-
- Class ptrlist
-
- We will use a ptrlist as an ivar in class selwindow to dynamically allocate
- pointers to objects that we add: to the windows list of objects.
-
- Class nullselect
-
- A nullSelect object will accept all of the standard protocol messages for the
- currentSel in a SelWindow. If we always subclass from nullselect then we are
- assured of not forgetting to define one of the required messages, even if we
- don't use it.
-
- Class primitiveWin
-
- A primitive window class with no frills. Won't work as a console. Note that
- this class is redundant with class window. We should be able to replace class
- window with this as long as we *never* try to use fwind. Class primitiveWin
- is used a the superclass for class selwindow.
-
-
-
- $x
-
- Class $x is a dictionary-based simple string class whose length may vary, up
- to a maximum of 255, but the maximum length is defined at instantiation. We
- cheat a bit here and use Mops' INDEXED class definition abilities and indexed
- ivar data area in a way that was not really intended.
-
- $x's are nice for use as string ivars, or if you want a persistent string object
- in the dictionary (no handles here so we don't need to do a new: and
- restore the data at each runtime).
-
- In class $x we take advantage of the fact that the 2-byte Width field for indexed
- objects can be used for other storage *if* we are careful. Since the
- Width field is only really needed here at instantiation (we are careful not to
- use words that rely on Width actually being the width), we use it here to
- store the maximum length, or limit, of text in the $x. Also, we now use the
- byte just prior to the indexed data area to store the length of the text, so
- it is easy to obtain a str255 format string since all we need do is obtain
- this address (which is idxbase - 1 , see get$: ).
-
- Note that there are still 4 unused bytes that might be used for pos and lim as
- in string. I guess we are safe doing this until Michael changes the internal
- structure of indexed objects. (!!)
-
-
- Rect+
- A rect+ is simply a rect that initializes its data to something other
- than zero. We also add some additional methods.
-
-
-
- Multiple Inheritance
- Multiple inheritance allows us to declare more than one superclass for any new
- class that we are defining. The new class will then inherit data and methods
- from its superclasses! We must be very careful to understand exactly which
-
- Now why, you may wonder, would we want to do this? The simple answer is because
- we are lazy and we want the programming system to do as much work as
- possible for us. Additionally, we actually save room in the dictionary (i.e.
- our program). Perhaps just as important as saving programming effort and program
- size, multiple inheritance is a very "natural" way for us to think about
- our objects. To make a biological analogy, a child may have inherited blue
- eyes from his mother and black hair from his father. To carry the analogy a
- bit further, we also would not expect the child to inherit both blue eyes and
- brown eyes (if the father had brown eyes), but only blue or only brown. So it
- is with multiple inheritance. When the same data exists in two different superclasses,
- only one of the superclass's data will be used. We won't carry
- the biological analogy any further because it likely will not hold (for example,
- we do not get "mixtures" of properties with programming multiple inheritance
- as we might get with some biological multiple inheritance). But, the
- point here is that multiple inheritance can be a very intuitive way to think
- about creating objects to solve problems.
-
- Multiple inheritance was once described as a very advanced feature that was
- only available in certain esoteric programming systems. Well, that may still
- be partially a true statement. But Mops provides a very easy to use multiple
- inheritance mechanism.
-
- So how does multiple inheritance save us work? By using multiple inheritance,
- we can easily create a new class whose objects perform just as we wish. By
- easy, we mean that we do not, in general, have to explicitly define every
- method. Often, we can define a new class with multiple inheritance simply by
- declaring the superclasses. Very little message defining will then be required.
-
- Consider an example. Let's say we wish to create a class of text that has the
- ability to display itself in a rectangle. We would like the coordinates of
- the rectangle to be part of the object and we would like to be able to move
- the rectangle around with move: and moveto: messages and also we would like
- all of the other rectangle messages to apply. But at the same time we wish to
- be able to use our string methods, such as new: and print: and so forth.
- Without multiple inheritance, we might singly inherit from one class, say the
- string class, but then we must create an ivar for the other class, in this
- case a rectangle class.
-
- :class stringRect super{ string+ }
- rect+ theRect
-
- :m classinit:
- 50 50 100 65 put: theRect ;m
-
- :m draw:
- addr: theRect
- draw: super ;m \ will use draw: method of class string+
-
- ;class
-
- So in our stringRect class defined above with single inheritance we have a
- class that will work. Since string+ is the superclass we automatically have
- all of the string+ methods available to any stringRect object without doing
- any more work. But, if we wish to use any of the rect+ methods, such as move:
- and moveto:, then we have some more programming work to do. Below we will add
- the move: and moveto methods to our stringRect class:
-
- :class stringRect super{ string+ }
- rect+ theRect
-
- :m classinit:
- 50 50 100 65 put: theRect ;m
-
- :m draw:
- addr: theRect
- draw: super ;m \ will use draw: method of class string+
-
- :m move: ( dx dy -- )
- move: theRect ;m
-
- :m moveto: ( x y -- )
- moveto: theRect ;m
-
- ;class
-
- Actually, that wasn't too bad, thanks to Mops's ability to send messages to
- ivars. But what about all of the other rect+ methods we would like to have
- available? Since ivar methods are not publicly available, we must write a
- similar "pass-through" method for each and every rect+ method we wish our
- stringRect objects to have! Ouch! Not difficult, but certainly very tedious.
- And also prone to typing errors. There must be a better way and of course there
- is, multiple inheritance!
-
- Now let's redefine our example with multiple inheritance:
-
- :class stringRect super{ string+ rect+ }
-
- :m classinit:
- 50 50 100 65 put: super> rect+
- ;m
-
- :m draw:
- addr: super> rect+
- 1 ( justification)
- draw: super ;m \ will use draw: method of class string+
-
- ;class
-